<?php
// Modify from phpbb
define('BACKUP_DIR', ROOT . 'www/backup/');

class AcpDatabase {

    var $u_action;

    function backup($action, $file_type) {
        global $aldb;
        $download = false;
        $store = false;

        @set_time_limit(1200);
        $time = time();
        $filename = 'arlicle_' . $time . '_' . unique_id();
        if ($action == 'download' || $action == 'store_and_download') {
            $download = true;
        }
        if ($action == 'store_file' || $action == 'store_and_download') {
            $store = true;
            if (!file_exists(BACKUP_DIR) || !is_writable(BACKUP_DIR)) {
                return 'File is not exists or is not writable.';
            }
        }
        $extractor = new MysqlExtractor($download, $store, $file_type, $filename, $time);
        $extractor->write_start(TABLE_PREFIX);
        $tables = $aldb->get_results("SHOW TABLES", ARRAY_N);
        foreach ($tables as $table_name) {
            $table_name = $table_name[0];
            $extractor->write_table($table_name);
            $extractor->write_data($table_name);
        }

        $extractor->write_end();
        return 'Backup done.';
    }

    function restore($file_name) {
        global $aldb;
        if (!preg_match('#^arlicle_\d{10,}_[a-z\d]{16}\.(sql(?:\.(?:gz|bz2))?)$#', $file_name, $matches)) {
            return 'The file is unsafe.';
        }

        $file_path =  BACKUP_DIR . $matches[0];
        if (!file_exists($file_path) || !is_readable($file_path)) {
            return 'File is not exists or is not readable.';
        }
        switch ($matches[1]) {
            case 'sql':
                $fp = fopen($file_path, 'rb');
                $read = 'fread';
                $seek = 'fseek';
                $eof = 'feof';
                $close = 'fclose';
                $fgetd = 'fgetd';
            break;

            case 'sql.bz2':
                $fp = bzopen($file_path, 'r');
                $read = 'bzread';
                $seek = '';
                $eof = 'feof';
                $close = 'bzclose';
                $fgetd = 'fgetd_seekless';
            break;

            case 'sql.gz':
                $fp = gzopen($file_path, 'rb');
                $read = 'gzread';
                $seek = 'gzseek';
                $eof = 'gzeof';
                $close = 'gzclose';
                $fgetd = 'fgetd';
            break;
        }
        while (($sql = $fgetd($fp, ";\n", $read, $seek, $eof)) !== false) {
            $aldb->query($sql);
        }
        return 'Restore done.';
    }

    function delete($file_name) {
        preg_match('#^arlicle_\d{10,}_[a-z\d]{16}\.(sql(?:\.(?:gz|bz2))?)$#', $file_name, $matches);
        $file_path =  BACKUP_DIR . $matches[0];
        if (!file_exists($file_path) || !is_writable($file_path)) {
            return 'File is not exists or is not able delete.';
        }
        if (unlink($file_path)) {
            return 'Delete done.';
        }
    }
}

class BaseExtractor {
    var $fh;
    var $fp;
    var $write;
    var $close;
    var $store;
    var $download;
    var $time;
    var $format;
    var $run_comp = false;

    function BaseExtractor($download = false, $store = false, $format, $filename, $time) {
        $this->download = $download;
        $this->store = $store;
        $this->time = $time;
        $this->format = $format;

        switch ($format) {
            case 'text':
                $ext = '.sql';
                $open = 'fopen';
                $this->write = 'fwrite';
                $this->close = 'fclose';
                $mimetype = 'text/x-sql';
            break;
            case 'bzip2':
                $ext = '.sql.bz2';
                $open = 'bzopen';
                $this->write = 'bzwrite';
                $this->close = 'bzclose';
                $mimetype = 'application/x-bzip2';
            break;
            case 'gzip':
                $ext = '.sql.gz';
                $open = 'gzopen';
                $this->write = 'gzwrite';
                $this->close = 'gzclose';
                $mimetype = 'application/x-gzip';
            break;
        }

        if ($download == true) {
            $name = $filename . $ext;
            header('Pragma: no-cache');
            header("Content-Type: $mimetype; name=\"$name\"");
            header("Content-disposition: attachment; filename=$name");
    
            switch ($format) {
                case 'bzip2':
                    ob_start();
                break;

                case 'gzip':
                    if ((isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) && strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'msie') === false) {
                        ob_start('ob_gzhandler');
                    } else {
                        $this->run_comp = true;
                    }
                break;
            }
        }
        
        if ($store == true) {
            $file = BACKUP_DIR . $filename . $ext;
    
            $this->fp = $open($file, 'w');
    
            if (!$this->fp) {
                trigger_error('Unable to write temporary file to storage folder', E_USER_ERROR);
            }
        }
    }

    function write_end() {
        static $close;
        if ($this->store) {
            if ($close === null) {
                $close = $this->close;
            }
            $close($this->fp);
        }

        // bzip2 must be written all the way at the end
        if ($this->download && $this->format === 'bzip2') {
            $c = ob_get_clean();
            echo bzcompress($c);
        }
    }

    function flush($data) {
        static $write;
        if ($this->store === true) {
            if ($write === null) {
                $write = $this->write;
            }
            $write($this->fp, $data);
        }

        if ($this->download === true) {
            if ($this->format === 'bzip2' || $this->format === 'text' || ($this->format === 'gzip' && !$this->run_comp)) {
                echo $data;
            }

            // we can write the gzip data as soon as we get it
            if ($this->format === 'gzip') {
                if ($this->run_comp) {
                    echo gzencode($data);
                } else {
                    ob_flush();
                    flush();
                }
            }
        }
    }
}

class MysqlExtractor extends BaseExtractor {

    function write_start($table_prefix) {
        $sql_data = "#\n";
        $sql_data .= "# Arlicle Backup Script\n";
        $sql_data .= "# Dump of tables for $table_prefix\n";
        $sql_data .= "# DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n";
        $sql_data .= "#\n";
        $this->flush($sql_data);
    }

    function write_table($table_name) {
        $this->old_write_table($table_name);
    }

    function write_data($table_name) {    
        $this->write_data_mysql($table_name);
    }

    function write_data_mysql($table_name) {
        global $aldb;
        $sql = "SELECT *
            FROM $table_name";
        $result = mysql_unbuffered_query($sql, $aldb->dbh);

        if ($result != false) {
            $fields_cnt = mysql_num_fields($result);

            // Get field information
            $field = array();
            for ($i = 0; $i < $fields_cnt; $i++) {
                $field[] = mysql_fetch_field($result, $i);
            }
            $field_set = array();
            
            for ($j = 0; $j < $fields_cnt; $j++) {
                $field_set[] = '`' .$field[$j]->name . '`';
            }

            $search            = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"');
            $replace        = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"');
            $fields            = implode(', ', $field_set);

            $sql_data        = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES ';
            $first_set        = true;
            $query_len        = 0;
            $max_len        = get_usable_memory();

            while ($row = mysql_fetch_row($result)) {
                $values = array();
                if ($first_set) {
                    $query = $sql_data . '(';
                } else {
                    $query  .= ',(';
                }

                for ($j = 0; $j < $fields_cnt; $j++) {
                    if (!isset($row[$j]) || is_null($row[$j])) {
                        $values[$j] = 'NULL';
                    } else if ($field[$j]->numeric && ($field[$j]->type !== 'timestamp')) {
                        $values[$j] = $row[$j];
                    } else {
                        $values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'";
                    }
                }
                $query .= implode(', ', $values) . ')';

                $query_len += strlen($query);
                if ($query_len > $max_len) {
                    $this->flush($query . ";\n\n");
                    $query = '';
                    $query_len = 0;
                    $first_set = true;
                } else {
                    $first_set = false;
                }
            }
            mysql_free_result($result);

            // check to make sure we have nothing left to flush
            if (!$first_set && $query) {
                $this->flush($query . ";\n\n");
            }
        }
    }

    function old_write_table($table_name) {
        global $aldb;
        $sql_data = "DROP TABLE IF EXISTS $table_name;\n";
        $result = $aldb->get_row("SHOW CREATE TABLE $table_name", ARRAY_N);
        $sql_data .= $result[1]. ";\n\n";
        $this->flush($sql_data);
    }
}

// modified from PHP.net
function fgetd(&$fp, $delim, $read, $seek, $eof, $buffer = 8192) {
    $record = '';
    $delim_len = strlen($delim);

    while (!$eof($fp)) {
        $pos = strpos($record, $delim);
        if ($pos === false) {
            $record .= $read($fp, $buffer);
            if ($eof($fp) && ($pos = strpos($record, $delim)) !== false) {
                $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR);
                return substr($record, 0, $pos);
            }
        } else {
            $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR);
            return substr($record, 0, $pos);
        }
    }

    return false;
}

function fgetd_seekless(&$fp, $delim, $read, $seek, $eof, $buffer = 8192) {
    static $array = array();
    static $record = '';

    if (!sizeof($array)) {
        while (!$eof($fp)) {
            if (strpos($record, $delim) !== false) {
                $array = explode($delim, $record);
                $record = array_pop($array);
                break;
            } else {
                $record .= $read($fp, $buffer);
            }
        }
        if ($eof($fp) && strpos($record, $delim) !== false) {
            $array = explode($delim, $record);
            $record = array_pop($array);
        }
    }

    if (sizeof($array)) {
        return array_shift($array);
    }

    return false;
}

// get how much space we allow for a chunk of data, very similar to phpMyAdmin's way of doing things ;-) (hey, we only do this for MySQL anyway :P)
function get_usable_memory()
{
    $val = trim(@ini_get('memory_limit'));

    if (preg_match('/(\\d+)([mkg]?)/i', $val, $regs)) {
        $memory_limit = (int) $regs[1];
        switch ($regs[2]) {

            case 'k':
            case 'K':
                $memory_limit *= 1024;
            break;

            case 'm':
            case 'M':
                $memory_limit *= 1048576;
            break;

            case 'g':
            case 'G':
                $memory_limit *= 1073741824;
            break;
        }

        // how much memory PHP requires at the start of export (it is really a little less)
        if ($memory_limit > 6100000) {
            $memory_limit -= 6100000;
        }

        // allow us to consume half of the total memory available
        $memory_limit /= 2;
    } else {
        // set the buffer to 1M if we have no clue how much memory PHP will give us :P
        $memory_limit = 1048576;
    }

    return $memory_limit;
}

function get_datafiles() {
    $files = array();
    if (!file_exists(BACKUP_DIR)) {
        return $files;
    }
    if ($handle = opendir(BACKUP_DIR)) {
        $i = 0;
        while (false !== ($file = readdir($handle))) {
            if ($file != '.' && $file != '..') {
                $files[$i]['file'] = $file;
                $files[$i]['size'] = al_get_size(filesize(BACKUP_DIR . $file));
                $i++;
                }
        }
        closedir($handle);
    }
    return $files;
}
?>
